5.05. Исключения
Исключения
Исключение (exception) — это событие, возникающее во время выполнения программы, которое нарушает нормальный ход выполнения и требует специальной обработки. Название «исключение» (exception) не случайно: оно означает отклонение от нормального сценария, а не просто «ошибку».
Ошибка (error) — это, как правило, системный сбой, который невозможно обработать (например, нехватка памяти). Исключение (exception) — это управляемое отклонение, которое можно предвидеть, перехватить и обработать.
Пример: Пользователь ввёл текст вместо числа. Это не «ошибка системы», а исключительная ситуация, которую программа может и должна обработать — например, попросить ввести число ещё раз. Таким образом, исключения — это не сбои, а часть логики приложения.
Все исключения в C# основаны на классе System.Exception. Это — базовый класс для всех исключений. .NET framework предоставляет класс System.Exception для обработки различных типов исключений, которые имеют место. Класс исключений является базовым классом среди других классов исключений.
System.Exception
├── System.SystemException (внутренние исключения .NET)
├── System.ApplicationException (устарело, не рекомендуется)
├── ArgumentException
│ ├── ArgumentNullException
│ └── ArgumentOutOfRangeException
├── InvalidOperationException
├── NullReferenceException
├── IOException
├── FormatException
└── DivideByZeroException
Все исключения — это объекты. Это значит, что у них есть свойства, методы и можно их расширять.
Основные блоки – try, catch, finally.
try {
// Блок кода, в котором может произойти ошибка
}
catch (ExceptionType ex) {
// Обработка исключения
}
finally {
// Код, который выполнится всегда (например, освобождение ресурсов)
}
try — блок с «опасным» кодом. Содержит код, который может выбросить исключение.
try
{
int number = int.Parse(input); // Может выбросить FormatException
Console.WriteLine(100 / number); // Может выбросить DivideByZeroException
}
catch — обработка исключения. Выполняется, если в try возникло исключение указанного типа.
catch (FormatException ex)
{
Console.WriteLine("Неверный формат числа.");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Деление на ноль!");
}
Можно ловить общее исключение:
catch (Exception ex)
{
Console.WriteLine($"Произошла ошибка: {ex.Message}");
}
Ловить Exception — последнее средство. Лучше указывать конкретные типы.
Можно использовать несколько catch-блоков, чтобы обработать разные типы исключений по-разному:
try {
// ...
} catch (FileNotFoundException ex) {
Console.WriteLine("Файл не найден: " + ex.Message);
} catch (UnauthorizedAccessException ex) {
Console.WriteLine("Нет доступа: " + ex.Message);
} catch (Exception ex) {
Console.WriteLine("Другая ошибка: " + ex.Message);
}
При наличии нескольких catch-блоков важен порядок. Более специфичные исключения должны идти раньше, чем общие.
finally — блок, который выполняется всегда. Выполняется в любом случае: было ли исключение, был ли return, break или goto.
finally
{
// Освобождение ресурсов
file?.Close();
connection?.Dispose();
}
Он используется для закрытия файлов, освобождения соединений, очистки ресурсов. Начиная с C# 8, для таких задач лучше использовать using, но finally всё ещё актуален.
throw — генерация исключения вручную
if (age < 0)
{
throw new ArgumentException("Возраст не может быть отрицательным.", nameof(age));
}
Можно выбрасывать:
- Новые исключения: throw new ArgumentException(...);
- Перебрасывать текущее: throw; (сохраняет стек-трейс);
- Перебрасывать с изменением: throw ex; (теряет оригинальный стек — плохо!).
Типы исключений:
| Исключение | Когда возникает? |
|---|---|
ArgumentException | Передан недопустимый аргумент |
ArgumentNullException | Аргумент равен null |
ArgumentOutOfRangeException | Аргумент выходит за пределы допустимого диапазона |
InvalidOperationException | Недопустимая операция в текущем состоянии объекта |
IOException | Ошибки ввода/вывода (файлы, сеть) |
NullReferenceException | Попытка вызвать метод у null-объекта |
IndexOutOfRangeException | Выход за границы массива |
FormatException | Неверный формат данных |
DivideByZeroException | Деление на ноль (для целых чисел) |
Совет: не ловите NullReferenceException — лучше проверяйте на null заранее.
Как читать стек-трейс (stack trace)?
Когда исключение не перехвачено, C# выводит стек-трейс — цепочку вызовов, по которой «всплывало» исключение. Для обычного пользователя это выглядит как «ошибка» с «крокозябрами», но нам нужно уметь их читать.
Пример:
System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)
at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)
at System.Int32.Parse(String s)
at MyApp.Program.ParseAge(String input) in Program.cs:line 15
at MyApp.Program.Main(String[] args) in Program.cs:line 8
Как читать:
Первая строка — тип исключения и сообщение.
Строки ниже — порядок вызовов «снизу вверх»:
- Main вызвал ParseAge
- ParseAge вызвал int.Parse
- int.Parse выбросил исключение.
:line XX — номер строки в файле.
Это главный инструмент при отладке - чтобы понять, в чём проблема, нужно пойти по цепочки снизу вверх, и верхний метод будет говорить об ошибке.
AggregateException — исключения в многопоточности. В асинхронном и параллельном программировании (например, Task.WhenAll, Parallel.ForEach) может возникнуть несколько исключений одновременно. .NET объединяет их в один объект — AggregateException.
Пример:
try
{
await Task.WhenAll(task1, task2, task3);
}
catch (AggregateException ex)
{
foreach (var inner in ex.InnerExceptions)
{
Console.WriteLine($"Ошибка: {inner.Message}");
}
}
AggregateException содержит коллекцию InnerExceptions, каждая из которых — отдельная ошибка.
Можно создавать свои классы исключений. Главное чтобы они наследовались от исключений.
public class InsufficientFundsException : Exception
{
public decimal Balance { get; }
public decimal Amount { get; }
public InsufficientFundsException(decimal balance, decimal amount)
: base($"Недостаточно средств: баланс {balance}, запрошено {amount}.")
{
Balance = balance;
Amount = amount;
}
}